package cn.xorange.commons.datasource.configuration;

import cn.hutool.core.util.ReflectUtil;
import cn.xorange.commons.data.annotation.MColumn;
import cn.xorange.commons.data.annotation.MTable;
import cn.xorange.commons.data.build.TableBuild;
import cn.xorange.commons.data.constants.FieldConstants;
import cn.xorange.commons.data.constants.KeyType;
import cn.xorange.commons.data.utils.ParamUtil;
import cn.xorange.commons.datasource.constants.DriverType;
import cn.xorange.commons.utils.spring.SpringUtils;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.handlers.AnnotationHandler;
import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler;
import com.baomidou.mybatisplus.core.metadata.OrderFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.*;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.parsing.PropertyParser;
import org.apache.ibatis.reflection.Reflector;
import org.apache.ibatis.session.Configuration;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import static cn.xorange.commons.data.build.TableBuild.getOwner;

/**
 * @author : yangjian
 * date : 2024/12/24
 * description :
 */
public class MyTableInfoHelper {

    private static final Log logger = LogFactory.getLog(TableInfoHelper.class);
    private static final Map<Class<?>, TableInfo> TABLE_INFO_CACHE = new ConcurrentHashMap();
    private static final Map<String, TableInfo> TABLE_NAME_INFO_CACHE = new ConcurrentHashMap();

    public static synchronized TableInfo initTableInfo(MapperBuilderAssistant builderAssistant, Class<?> clazz) {
        TableInfo targetTableInfo = (TableInfo)TABLE_INFO_CACHE.get(clazz);
        Configuration configuration = builderAssistant.getConfiguration();
        if (targetTableInfo != null) {
            Configuration oldConfiguration = targetTableInfo.getConfiguration();
            if (!oldConfiguration.equals(configuration)) {
                targetTableInfo = initTableInfo(configuration, builderAssistant.getCurrentNamespace(), clazz);
            }
        } else {
            targetTableInfo = initTableInfo(configuration, builderAssistant.getCurrentNamespace(), clazz);
        }
        try {
            Field infoField = TableInfoHelper.class.getDeclaredField("TABLE_INFO_CACHE");
            infoField.setAccessible(true);
            Field modInfoField = infoField.getClass().getDeclaredField("modifiers");
            modInfoField.setAccessible(true);
            modInfoField.setInt(infoField,infoField.getModifiers()& ~Modifier.FINAL);
            infoField.set(TableInfoHelper.class, TABLE_INFO_CACHE);

            Field nameField = TableInfoHelper.class.getDeclaredField("TABLE_INFO_CACHE");
            infoField.setAccessible(true);
            Field modNameField = nameField.getClass().getDeclaredField("modifiers");
            modNameField.setAccessible(true);
            modNameField.setInt(nameField,nameField.getModifiers()& ~Modifier.FINAL);
            nameField.set(TableInfoHelper.class, TABLE_NAME_INFO_CACHE);

        }catch (Exception e){
            //
        }

        return  targetTableInfo;
    }


    private static synchronized TableInfo initTableInfo(Configuration configuration, String currentNamespace, Class<?> clazz) {
        GlobalConfig globalConfig = GlobalConfigUtils.getGlobalConfig(configuration);
        PostInitTableInfoHandler postInitTableInfoHandler = globalConfig.getPostInitTableInfoHandler();
        TableInfo tableInfo = postInitTableInfoHandler.creteTableInfo(configuration, clazz);
        //tableInfo.setCurrentNamespace(currentNamespace);
        ReflectUtil.setFieldValue(tableInfo,"currentNamespace",currentNamespace);
        String[] excludeProperty = initTableName(clazz, globalConfig, tableInfo);
        List<String> excludePropertyList = excludeProperty != null && excludeProperty.length > 0 ? Arrays.asList(excludeProperty) : Collections.emptyList();
        initTableFields(configuration, clazz, globalConfig, tableInfo, excludePropertyList);
        //tableInfo.initResultMapIfNeed();
        try {
            Method method = TableInfo.class.getDeclaredMethod("initResultMapIfNeed");
            method.setAccessible(true);
            method.invoke(tableInfo);
        }catch (Exception e){
            System.out.println("tableInfo initResultMapIfNeed 出错");
        }
        postInitTableInfoHandler.postTableInfo(tableInfo, configuration);
        TABLE_INFO_CACHE.put(clazz, tableInfo);
        TABLE_NAME_INFO_CACHE.put(tableInfo.getTableName(), tableInfo);
        LambdaUtils.installCache(tableInfo);
        return tableInfo;
    }

    private static String[] initTableName(Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo) {
        GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
        AnnotationHandler annotationHandler = globalConfig.getAnnotationHandler();
        TableName table = (TableName)annotationHandler.getAnnotation(clazz, TableName.class);
        Configuration configuration = tableInfo.getConfiguration();
        String tableName = clazz.getSimpleName();
        String tablePrefix = dbConfig.getTablePrefix();
        String schema = dbConfig.getSchema();
        boolean tablePrefixEffect = true;
        String[] excludeProperty = null;
        if (table != null) {
            if (StringUtils.isNotBlank(table.value())) {
                tableName = PropertyParser.parse(table.value(), configuration.getVariables());
                if (StringUtils.isNotBlank(tablePrefix) && !table.keepGlobalPrefix()) {
                    tablePrefixEffect = false;
                }
            } else {
                tableName = initTableNameWithDbConfig(tableName, dbConfig);

            }

            if (StringUtils.isNotBlank(table.schema())) {
                schema = PropertyParser.parse(table.schema(), configuration.getVariables());
            }

            if (StringUtils.isNotBlank(table.resultMap())) {
                //tableInfo.setResultMap(table.resultMap());
                ReflectUtil.setFieldValue(tableInfo,"resultMap",table.resultMap());
            }

            //tableInfo.setAutoInitResultMap(table.autoResultMap());
            ReflectUtil.setFieldValue(tableInfo,"autoInitResultMap",table.autoResultMap());
            excludeProperty = table.excludeProperty();
        } else {
            tableName = initTableNameByMTable(clazz, dbConfig.isTableUnderline());
        }

        String targetTableName = tableName;
        if (StringUtils.isNotBlank(tablePrefix) && tablePrefixEffect) {
            targetTableName = tablePrefix + tableName;
        }

        String tableFormat = dbConfig.getTableFormat();
        if (StringUtils.isNotBlank(tableFormat)) {
            targetTableName = String.format(tableFormat, targetTableName);
        }

        if (StringUtils.isNotBlank(schema)) {
            targetTableName = schema + "." + targetTableName;
        }

        //tableInfo.setTableName(targetTableName);
        ReflectUtil.setFieldValue(tableInfo,"tableName",targetTableName);
        if (CollectionUtils.isNotEmpty(dbConfig.getKeyGenerators())) {
            //tableInfo.setKeySequence((KeySequence)annotationHandler.getAnnotation(clazz, KeySequence.class));
            ReflectUtil.setFieldValue(tableInfo,"keySequence", annotationHandler.getAnnotation(clazz, KeySequence.class));
        }

        return excludeProperty;
    }

    private static String initTableNameWithDbConfig(String className, GlobalConfig.DbConfig dbConfig) {
        String tableName = className;
        if (dbConfig.isTableUnderline()) {
            tableName = StringUtils.camelToUnderline(className);
        }

        if (dbConfig.isCapitalMode()) {
            tableName = tableName.toUpperCase();
        } else {
            tableName = StringUtils.firstToLowerCase(tableName);
        }

        return tableName;
    }

    public static String initTableNameByMTable(Class<?> clazz, boolean isUnderline) {

        String name;
        //如果有MTable注释 则使用注释中的表名称
        if(clazz.isAnnotationPresent(MTable.class)){
            MTable table= clazz.getAnnotation(MTable.class);
            //如果表名称为空
            if (table.tab().isEmpty()){
                name = clazz.getSimpleName();
                if(Boolean.TRUE.equals(isUnderline)){
                    name = cn.xorange.commons.utils.lang.StringUtils.toUnderlineCase(name);
                }
            }else {
                //不为空则使用MTable注释表名称
                name = table.tab();
            }
            //if(isSelect && !table.alias().isEmpty()){
            //    name = StringUtils.format("{} {}",name,table.alias());
            //}
        }else {
            name = clazz.getSimpleName();
            if(Boolean.TRUE.equals(isUnderline)){
                name = cn.xorange.commons.utils.lang.StringUtils.toUnderlineCase(name);
            }
        }
        String dsType = SpringUtils.getBean(DSConfig.class).getDsType();
        if (DriverType.ORACLE.equals(dsType)){
            String owner = getOwner(clazz);
            if(!owner.isEmpty()){
                name = String.format("%s.%s",owner,name);
            }
        }
        return name;
    }

    private static void initTableFields(Configuration configuration, Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo, List<String> excludeProperty) {
        AnnotationHandler annotationHandler = globalConfig.getAnnotationHandler();
        PostInitTableInfoHandler postInitTableInfoHandler = globalConfig.getPostInitTableInfoHandler();
        Reflector reflector = tableInfo.getReflector();
        List<Field> list = getAllFields(clazz, annotationHandler);
        boolean isReadPK = false;
        boolean existTableId = isExistTableId(clazz, list, annotationHandler);
        boolean existTableLogic = isExistTableLogic(clazz, list, annotationHandler);
        List<TableFieldInfo> fieldList = new ArrayList(list.size());
        Iterator var13 = list.iterator();

        while(var13.hasNext()) {
            Field field = (Field)var13.next();
            if (!excludeProperty.contains(field.getName())) {
                boolean isPK = false;
                OrderBy orderBy = (OrderBy)annotationHandler.getAnnotation(field, OrderBy.class);
                boolean isOrderBy = orderBy != null;
                if (existTableId) {
                    MColumn mColumn = field.getAnnotation(MColumn.class);
                    if(Objects.nonNull(mColumn) && !mColumn.key().equals(KeyType.NOT)){
                        initTableIdWithMColumn(tableInfo, field, mColumn);
                        isReadPK = true;
                        isPK = true;
                    }else {
                        TableId tableId = annotationHandler.getAnnotation(field, TableId.class);
                        if (tableId != null) {
                            if (isReadPK) {
                                throw ExceptionUtils.mpe("@TableId can't more than one in Class: \"%s\".", new Object[]{clazz.getName()});
                            }
                            initTableIdWithAnnotation(globalConfig, tableInfo, field, tableId);
                            isReadPK = true;
                            isPK = true;
                        }
                    }
                } else if (!isReadPK) {
                    // 使用config配置的key Id
                    isPK = isReadPK = initTableIdWithoutAnnotation(globalConfig, tableInfo, field);
                }

                if (isPK) {
                    if (orderBy != null) {
                        tableInfo.getOrderByFields().add(new OrderFieldInfo(tableInfo.getKeyColumn(), orderBy.asc(), orderBy.sort()));
                    }
                } else {
                    TableField tableField = annotationHandler.getAnnotation(field, TableField.class);
                    TableFieldInfo tableFieldInfo;
                    if (tableField != null) {
                        tableFieldInfo = new TableFieldInfo(globalConfig, tableInfo, field, tableField, reflector, existTableLogic, isOrderBy);
                        fieldList.add(tableFieldInfo);
                        postInitTableInfoHandler.postFieldInfo(tableFieldInfo, configuration);
                    } else {
                        //与原mybatisPlus逻辑不同，mybatisPlus将没有TableField注解的属性都设定为数据列与MColumn冲突，此处只识别MColumn
                        tableFieldInfo = tableFieldInfoByMColumn(globalConfig,tableInfo,field, existTableLogic, isOrderBy);
                        if(Objects.nonNull(tableFieldInfo)){
                            fieldList.add(tableFieldInfo);
                            postInitTableInfoHandler.postFieldInfo(tableFieldInfo, configuration);
                        }

                    }
                }
            }
        }

        //tableInfo.setFieldList(fieldList);
        try {

            Method method = TableInfo.class.getDeclaredMethod("setFieldList",List.class);
            method.setAccessible(true);
            method.invoke(tableInfo,fieldList);
        }catch (Exception e){
            System.out.println("tableInfo setFieldList 出错");
        }
        //if (!isReadPK) {
        //    logger.warn(String.format("Can not find table primary key in Class: \"%s\".", clazz.getName()));
        //}

    }


    public static List<Field> getAllFields(Class<?> clazz, AnnotationHandler annotationHandler) {
        List<Field> fieldList = ReflectionKit.getFieldList(ClassUtils.getUserClass(clazz));
        List<Field> tableFieldList = new ArrayList<>();
        for(Field f :fieldList){
            TableField tableField = annotationHandler.getAnnotation(f, TableField.class);
            if(tableField == null || tableField.exist()){
                tableFieldList.add(f);
            }
        }
        List<Field> mFieldList = new ArrayList<>();
        for (Field f : fieldList){
            MColumn mColumn = f.getAnnotation(MColumn.class);
            if(Objects.nonNull(mColumn)){
                mFieldList.add(f);
            }
        }
        for (Field mf : mFieldList){
            boolean have = false;
            for (Field f : tableFieldList){
                if(mf.getName().equals(f.getName())){
                    have = true;
                    break;
                }
            }
            if(!have){
                tableFieldList.add(mf);
            }else {
                have = false;
            }
        }
        return tableFieldList;
    }

    public static boolean isExistTableId(Class<?> clazz, List<Field> list, AnnotationHandler annotationHandler) {
        boolean isTableId = false;
        Field keyField = ParamUtil.getKeyField(clazz);
        if(Objects.nonNull(keyField)){
            return true;
        }
        isTableId = list.stream().anyMatch((field) -> {
            return annotationHandler.isAnnotationPresent(field, TableId.class);
        });
        return isTableId;
    }

    public static boolean isExistTableLogic(Class<?> clazz, List<Field> list, AnnotationHandler annotationHandler) {
        boolean isLogic = TableBuild.isLogicDel(clazz);
        if(!isLogic){
            isLogic = list.stream().anyMatch((field) -> annotationHandler.isAnnotationPresent(field, TableLogic.class));
        }
        return isLogic;
    }



    // 使用 TableId 配置key
    private static void initTableIdWithAnnotation(GlobalConfig globalConfig, TableInfo tableInfo, Field field, TableId tableId) {
        GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
        boolean underCamel = tableInfo.isUnderCamel();
        String property = field.getName();
        if (globalConfig.getAnnotationHandler().isAnnotationPresent(field, TableField.class)) {
            logger.warn(String.format("This \"%s\" is the table primary key by @TableId annotation in Class: \"%s\",So @TableField annotation will not work!", property, tableInfo.getEntityType().getName()));
        }

        if (IdType.NONE == tableId.type()) {
            //tableInfo.setIdType(dbConfig.getIdType());
            ReflectUtil.setFieldValue(tableInfo,"idType",dbConfig.getIdType());
        } else {
            //tableInfo.setIdType(tableId.type());
            ReflectUtil.setFieldValue(tableInfo,"idType",tableId.type());
        }

        String column = property;
        if (StringUtils.isNotBlank(tableId.value())) {
            column = tableId.value();
        } else {
            if (underCamel) {
                column = StringUtils.camelToUnderline(property);
            }

            if (dbConfig.isCapitalMode()) {
                column = column.toUpperCase();
            }
        }

        Class<?> keyType = tableInfo.getReflector().getGetterType(property);
        if (keyType.isPrimitive()) {
            logger.warn(String.format("This primary key of \"%s\" is primitive !不建议如此请使用包装类 in Class: \"%s\"", property, tableInfo.getEntityType().getName()));
        }

        if (StringUtils.isEmpty(tableId.value())) {
            String columnFormat = dbConfig.getColumnFormat();
            if (StringUtils.isNotBlank(columnFormat)) {
                column = String.format(columnFormat, column);
            }
        }

        //tableInfo.setKeyRelated(checkRelated(underCamel, property, column))
        //        .setKeyColumn(column)
        //        .setKeyProperty(property)
        //        .setKeyType(keyType);
        ReflectUtil.setFieldValue(tableInfo,"keyRelated", checkRelated(underCamel, property, column));
        ReflectUtil.setFieldValue(tableInfo, "keyColumn", column);
        ReflectUtil.setFieldValue(tableInfo, "keyProperty", property);
        ReflectUtil.setFieldValue(tableInfo,"keyType", keyType);
    }

    private static void initTableIdWithMColumn(TableInfo tableInfo, Field field,MColumn mColumn){
        IdType idType = IdType.INPUT;
        if(KeyType.AUTO.equals(mColumn.key())){
            idType = IdType.AUTO;
        }
        String property = field.getName();
        String column = property;
        if(StringUtils.isNotBlank(mColumn.col())){
            column = mColumn.col();
        }
        if(tableInfo.isUnderCamel()){
            column = StringUtils.camelToUnderline(column);
        }
        Class<?> keyType = tableInfo.getReflector().getGetterType(property);
        ReflectUtil.setFieldValue(tableInfo,"idType", idType);
        ReflectUtil.setFieldValue(tableInfo,"keyRelated", checkRelated(tableInfo.isUnderCamel(), property, column));
        ReflectUtil.setFieldValue(tableInfo, "keyColumn", column);
        ReflectUtil.setFieldValue(tableInfo, "keyProperty", property);
        ReflectUtil.setFieldValue(tableInfo,"keyType", keyType);
    }

    // 使用config配置的key Id
    private static boolean initTableIdWithoutAnnotation(GlobalConfig globalConfig, TableInfo tableInfo, Field field) {
        GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
        String property = field.getName();
        if ("id".equalsIgnoreCase(property)) {
            if (globalConfig.getAnnotationHandler().isAnnotationPresent(field, TableField.class)) {
                logger.warn(String.format("This \"%s\" is the table primary key by default name for `id` in Class: \"%s\",So @TableField will not work!", property, tableInfo.getEntityType().getName()));
            }

            String column = property;
            if (dbConfig.isCapitalMode()) {
                column = property.toUpperCase();
            }

            Class<?> keyType = tableInfo.getReflector().getGetterType(property);
            if (keyType.isPrimitive()) {
                logger.warn(String.format("This primary key of \"%s\" is primitive !不建议如此请使用包装类 in Class: \"%s\"", property, tableInfo.getEntityType().getName()));
            }

            String columnFormat = dbConfig.getColumnFormat();
            if (StringUtils.isNotBlank(columnFormat)) {
                column = String.format(columnFormat, column);
            }

            //tableInfo.setKeyRelated(checkRelated(tableInfo.isUnderCamel(), property, column))
            // .setIdType(dbConfig.getIdType())
            // .setKeyColumn(column)
            // .setKeyProperty(property)
            // .setKeyType(keyType);
            ReflectUtil.setFieldValue(tableInfo,"keyRelated", checkRelated(tableInfo.isUnderCamel(), property, column));
            ReflectUtil.setFieldValue(tableInfo, "idType", dbConfig.getIdType());
            ReflectUtil.setFieldValue(tableInfo, "keyColumn", column);
            ReflectUtil.setFieldValue(tableInfo, "keyProperty", property);
            ReflectUtil.setFieldValue(tableInfo,"keyType", keyType);
            return true;
        } else {
            return false;
        }
    }

    public static boolean checkRelated(boolean underCamel, String property, String column) {
        column = StringUtils.getTargetColumn(column);
        String propertyUpper = property.toUpperCase(Locale.ENGLISH);
        String columnUpper = column.toUpperCase(Locale.ENGLISH);
        if (!underCamel) {
            return !propertyUpper.equals(columnUpper);
        } else {
            return !propertyUpper.equals(columnUpper) && !propertyUpper.equals(columnUpper.replace("_", ""));
        }
    }


    public static TableFieldInfo tableFieldInfoByMColumn(GlobalConfig globalConfig, TableInfo tableInfo, Field field, boolean isTableLogic,boolean isOrderBy){
        TableFieldInfo tableFieldInfo = new TableFieldInfo(globalConfig, tableInfo, field, tableInfo.getReflector(), isTableLogic, isOrderBy);
        MColumn mColumn = field.getAnnotation(MColumn.class);
        if(Objects.nonNull(mColumn)){
            String colName = ParamUtil.getColName(field, tableInfo.isUnderCamel());
            ReflectUtil.setFieldValue(tableFieldInfo, "column", colName);
            ReflectUtil.setFieldValue(tableFieldInfo, "sqlSelect", colName);
            setFillField(field,tableFieldInfo);
            if(FieldConstants.LOGIC_DEL.equals(field.getName())){
                if(isTableLogic){
                    GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
                    ReflectUtil.setFieldValue(tableFieldInfo, "logicDelete", true);
                    ReflectUtil.setFieldValue(tableFieldInfo, "logicNotDeleteValue", dbConfig.getLogicNotDeleteValue());
                    ReflectUtil.setFieldValue(tableFieldInfo, "logicDeleteValue", dbConfig.getLogicDeleteValue());
                }else {
                    return null;
                }
            }
            return tableFieldInfo;
        }else {
            return null;
        }

    }

    private static void setFillField(Field field, TableFieldInfo tableFieldInfo){
        for(String name : MyBatisPlusConfig.FILL_INSERT_FIELDS){
            if(name.equals(field.getName())){
                ReflectUtil.setFieldValue(tableFieldInfo,"fieldFill", FieldFill.INSERT);
                ReflectUtil.setFieldValue(tableFieldInfo, "withInsertFill",true);
            }
        }
        for(String name : MyBatisPlusConfig.FILL_UPDATE_FIELDS){
            if(name.equals(field.getName())){
                ReflectUtil.setFieldValue(tableFieldInfo,"fieldFill", FieldFill.UPDATE);
                ReflectUtil.setFieldValue(tableFieldInfo, "withUpdateFill",true);
            }
        }
    }


    public static String getKeyFieldName(Class<?> clazz,boolean isMapUnderscoreToCamelCase){
        Field[] fields = ReflectUtil.getFields(clazz);
        for (Field f : fields){
            MColumn mColumn = f.getAnnotation(MColumn.class);
            if (Objects.nonNull(mColumn) && !KeyType.NOT.equals(mColumn.key())){
                return ParamUtil.getColName(f,mColumn,isMapUnderscoreToCamelCase);
            }
            TableId tableId = f.getAnnotation(TableId.class);
            if(Objects.nonNull(tableId)){
                String property = f.getName();
                if (StringUtils.isNotBlank(tableId.value())) {
                    return tableId.value();
                } else {
                    if (isMapUnderscoreToCamelCase) {
                        return StringUtils.camelToUnderline(property);
                    }else {
                        return property;
                    }
                }
            }
        }
        return null;
    }
}




